//===============================================================================
// Microsoft Aspiring Software Architects Program
// E-Appointments - Case Study Implementation
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections;

using System.Windows.Media.Animation;

namespace EAppointmentsWPF
{
    public class EAppointmentsPanel : StackPanel
    {
        #region constants

        private const double MILLISECS_PER_MINUTE = 60000;

        #endregion

        #region Dependency Properties

        #region TimeProperty

        public static readonly DependencyProperty TimeProperty = DependencyProperty.RegisterAttached("Time",
            typeof(DateTime), typeof(EAppointmentsPanel), new FrameworkPropertyMetadata(DateTime.Now, FrameworkPropertyMetadataOptions.AffectsArrange));

        public static void SetTime(DependencyObject depObj, DateTime time)
        {
            depObj.SetValue(TimeProperty, time);
        }

        public static DateTime GetTime(DependencyObject depObj)
        {
            return (DateTime)depObj.GetValue(TimeProperty);
        }

        #endregion

        #region BeginDisplayTimeProperty

        public static readonly DependencyProperty BeginDisplayTimeProperty = DependencyProperty.Register("BeginDisplayTime",
            typeof(DateTime), typeof(EAppointmentsPanel),
            new FrameworkPropertyMetadata(new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, 7, 0, 0),
            new PropertyChangedCallback(BeginDisplayTimePropertyChanged)));

        public DateTime BeginDisplayTime
        {
            get { return (DateTime)this.GetValue(BeginDisplayTimeProperty); }
            set { this.SetValue(BeginDisplayTimeProperty, value); }
        }

        private static void BeginDisplayTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EAppointmentsPanel dnp = d as EAppointmentsPanel;
            DateTime beginDisplayTime = (DateTime)e.NewValue;

            dnp.beginTimeSpan = new TimeSpan(beginDisplayTime.TimeOfDay.Ticks);

            dnp.CalculatePixelsPerMinute();
        }

        #endregion

        #region EndDisplayTimeProperty

        public static readonly DependencyProperty EndDisplayTimeProperty = DependencyProperty.Register("EndDisplayTime",
            typeof(DateTime), typeof(EAppointmentsPanel),
            new FrameworkPropertyMetadata(new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, 22, 0, 0),
            new PropertyChangedCallback(EndDisplayTimePropertyChanged)));

        public DateTime EndDisplayTime
        {
            get { return (DateTime)this.GetValue(EndDisplayTimeProperty); }
            set { this.SetValue(EndDisplayTimeProperty, value); }
        }

        private static void EndDisplayTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EAppointmentsPanel dnp = d as EAppointmentsPanel;
            DateTime endDisplayTime = (DateTime)e.NewValue;

            dnp.endTimeSpan = new TimeSpan(endDisplayTime.TimeOfDay.Ticks);

            dnp.CalculatePixelsPerMinute();
        }

        #endregion

        #region HiddenProperty

        public static readonly DependencyProperty HiddenProperty = DependencyProperty.RegisterAttached("Hidden",
            typeof(bool), typeof(EAppointmentsPanel), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnChildHiddenChanged)));

        public static void SetHidden(DependencyObject depObj, bool hidden)
        {
            depObj.SetValue(HiddenProperty, hidden);
        }

        public static bool GetHidden(DependencyObject depObj)
        {
            return (bool)depObj.GetValue(HiddenProperty);
        }

        private static void OnChildHiddenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EAppointmentsPanel dnp = VisualTreeHelper.GetParent(d) as EAppointmentsPanel;

            //if the elements parent is not a EAppointmentsPanel don't do anything
            if (dnp != null)
            {
                dnp.InvalidateArrange();
            }
        }

        #endregion

        #region IsDynamicLayoutEnabledProperty

        public static readonly DependencyProperty IsDynamicLayoutEnabledProperty =
            DependencyProperty.Register("IsDynamicLayoutEnabled", typeof(bool), typeof(EAppointmentsPanel),
            new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange));

        public bool IsDynamicLayoutEnabled
        {
            get { return (bool)GetValue(IsDynamicLayoutEnabledProperty); }
            set { SetValue(IsDynamicLayoutEnabledProperty, value); }
        }

        #endregion

        #region PixelsPerHourProperty

        public static readonly DependencyProperty PixelsPerHourProperty =
            DependencyProperty.Register("PixelsPerHour", typeof(double), typeof(EAppointmentsPanel), new UIPropertyMetadata(0.0));

        public double PixelsPerHour
        {
            get { return (double)GetValue(PixelsPerHourProperty); }
            set { SetValue(PixelsPerHourProperty, value); }
        }

        #endregion

        #region TimelineViewProperty

        public bool TimelineView
        {
            get { return (bool)GetValue(TimelineViewProperty); }
            set { SetValue(TimelineViewProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TimelineViewProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TimelineViewProperty =
            DependencyProperty.Register("TimelineView", typeof(bool), typeof(EAppointmentsPanel), new UIPropertyMetadata(false));

        #endregion

        #endregion

        #region constructors

        public EAppointmentsPanel()
        {
            arrangedChildren = new Dictionary<UIElement, Rect>();

            this.beginTimeSpan = new TimeSpan(this.BeginDisplayTime.TimeOfDay.Ticks);
            this.endTimeSpan = new TimeSpan(this.EndDisplayTime.TimeOfDay.Ticks);
        }

        #endregion

        #region private fields

        private TimeSpan endTimeSpan = TimeSpan.Zero;
        private TimeSpan beginTimeSpan = TimeSpan.Zero;
        private double pixelsPerMinute;
        private Dictionary<UIElement, Rect> arrangedChildren;

        #endregion

        #region Layout method overrides

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (!this.TimelineView)
            {
                for (int i = 0; i < this.InternalChildren.Count; i++)
                {
                    UIElement element = this.InternalChildren[i];

                    if (EAppointmentsPanel.GetHidden(element))
                    {
                        element.SetValue(VisibilityProperty, Visibility.Collapsed);
                    }
                    else
                    {
                        element.SetValue(VisibilityProperty, Visibility.Visible);
                    }
                }

                return base.ArrangeOverride(finalSize);
            }
            else
            {
                double currentX = 0.0;
                double currentY = 0.0;
                Size childSize;
                bool animatingTransition = false;
                Dictionary<UIElement, Rect> previousChildLocations = new Dictionary<UIElement, Rect>(this.arrangedChildren);
                this.arrangedChildren.Clear();

                //layout based on a "timeline"
                for (int i = 0; i < this.InternalChildren.Count; i++)
                {
                    animatingTransition = false;

                    UIElement element = this.InternalChildren[i];

                    if (EAppointmentsPanel.GetHidden(element))
                    {
                        element.SetValue(VisibilityProperty, Visibility.Collapsed);

                        childSize = element.DesiredSize;

                        element.Arrange(new Rect(currentX, currentY, childSize.Width, childSize.Height));
                    }
                    else
                    {
                        element.SetValue(VisibilityProperty, Visibility.Visible);

                        currentY = GetYBasedOnTime(element);
                        currentX = GetXBasedOnOtherItems(element, currentY);

                        DoubleAnimation xAnimation = null;
                        DoubleAnimation yAnimation = null;

                        if (previousChildLocations.ContainsKey(element))
                        {
                            Rect previousPosition = previousChildLocations[element];

                            if (previousPosition.X != currentX || previousPosition.Y != currentY)
                            {
                                xAnimation = new DoubleAnimation(0, new Duration(TimeSpan.FromMilliseconds(200)));
                                yAnimation = new DoubleAnimation(0, new Duration(TimeSpan.FromMilliseconds(200)));

                                element.RenderTransform = new TranslateTransform(previousPosition.X - currentX, previousPosition.Y - currentY);

                                animatingTransition = true;
                            }
                        }

                        childSize = element.DesiredSize;

                        element.Arrange(new Rect(currentX, currentY, childSize.Width, childSize.Height));

                        if (animatingTransition)
                        {
                            element.RenderTransform.BeginAnimation(TranslateTransform.XProperty, xAnimation);
                            element.RenderTransform.BeginAnimation(TranslateTransform.YProperty, yAnimation);
                        }
                    }
                }

                return finalSize;
            }
        }

        #endregion

        #region other overrides

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);

            if (e.Property == ActualHeightProperty)
            {
                this.CalculatePixelsPerMinute();
            }
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);

            this.InvalidateArrange();
        }

        #endregion

        #region public properties

        #endregion

        #region private methods

        private double GetXBasedOnOtherItems(UIElement element, double yValue)
        {
            Rect elementRect = new Rect(0, yValue, element.DesiredSize.Width, element.DesiredSize.Height);

            foreach (Rect rect in this.arrangedChildren.Values)
            {
                while (Rect.Intersect(rect, elementRect) != Rect.Empty)
                {
                    elementRect.X += rect.Width + 3;
                }
            }

            this.arrangedChildren.Add(element, elementRect);

            return elementRect.X;
        }

        private void CalculatePixelsPerMinute()
        {
            TimeSpan diff = this.endTimeSpan - this.beginTimeSpan;

            double totalMinutes = diff.TotalMilliseconds / MILLISECS_PER_MINUTE;
            this.pixelsPerMinute = this.ActualHeight / totalMinutes;

            //set the public property indicating the pixels per hour
            this.PixelsPerHour = this.pixelsPerMinute * 60.0;
        }

        private double GetYBasedOnTime(UIElement element)
        {
            DateTime childTime = (DateTime)element.GetValue(EAppointmentsPanel.TimeProperty);
            TimeSpan childTimeSpan = new TimeSpan(childTime.TimeOfDay.Ticks);

            if (childTime.Hour != 0)
            {
                double childMinuteDiffFromBeginning = childTimeSpan.TotalMinutes - this.beginTimeSpan.TotalMinutes;

                double y = (childMinuteDiffFromBeginning * pixelsPerMinute);

                return y;
            }
            else
            {
                return 0;
            }
        }

        #endregion
    }
}
